package com.cacho.s2b.lesson.engine;

import com.cacho.s2b.lesson.util.CsvUtils;
import com.cacho.s2b.lesson.utils.Constants;
import com.cacho.s2b.lesson.vo.*;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.ini4j.Ini;
import org.ini4j.Profile;

import java.io.*;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

/**
 * @Description 接口请求数据初始化 getInstance()单例
 * @Author LingWei
 * @date 2023/12/31
 **/
@Setter
@Getter
@Slf4j
public class ApiInfoHub {
    private final String API_PREFIX = "api.";  // api指向文件 api.def = data/def/api_def.csv
    private final String DB_PREFIX = "db.";  //数据库配置指向 db.
    private static final String MAPPING_FOLDER = "mappingpath"; // 接口对应mapping文件目录
    private Map<String, Map<String, String>> mappingMaps = new HashMap<>(); // 接口名=接口请求模板map(接口的body参数)

    private static ApiInfoHub apiInfoHub = null;
    private EnvInfo envInfo = new EnvInfo();  // 必须要 new EnvInfo()
    private Map<String, ApiRunInfo> apiRunInfoMap = new HashMap<>(); // 接口名=ApiRunInfo
    private Map<String, ApiActivation> activationMap = new HashMap<>(); // 接口需要的证书


    public static ApiInfoHub getInstance() {
        if (apiInfoHub == null) {
            apiInfoHub = new ApiInfoHub();
        }
        return apiInfoHub;
    }

    public ApiInfoHub() {
        this("/data/config.ini");
    }

    public ApiInfoHub(String iniFile) {
        initApiTesting(iniFile);
    }

    private void initApiTesting(String initFile) {
        Ini ini = new Ini();
        InputStream in = getClass().getResourceAsStream(initFile);
        try {
            ini.load(in);
            String runEnv = ini.get("run", "run.env"); // section,key
            String overrideEnv = System.getenv("ENV");  // 得到ENV的环境变量
            log.debug("运行环境为：" + overrideEnv);
            runEnv = StringUtils.isEmpty(overrideEnv) ? runEnv : overrideEnv;
            // 1.EnvInfo 赋值
            envInfo.setName(runEnv.toLowerCase());
            Profile.Section section = ini.get(runEnv.toLowerCase()); // section 是map对象
            // 1.EnvInfo 赋值
            envInfo.setDescription(section.get("desc")); // section下面的key

            // 遍历对应环境下 section 的key,value
            final String SERVER_PREFIX = "server.";  // 服务器访问的ip
            Map<String, String> mapServer = new HashMap<>();
            Map<String, String> mapHeaders = new HashMap<>();
            final String HEADERS_PREFIX = "headers."; // 公共headers

            ApiMetaInfo apiMetaInfo = new ApiMetaInfo();

            // 1.EnvInfo 赋值
            Map<String, DBConnectionInfo> connectionInfoMap = new HashMap<>(); //数据库名=数据库连接信息
            envInfo.setConnectionInfoMap(connectionInfoMap);

            for (Map.Entry<String, String> e : section.entrySet()) {
                String k = e.getKey().trim();   // 获取key值
                String v = e.getValue().trim(); // 获取value值
                if (k.startsWith(SERVER_PREFIX)) {
                    mapServer.put(k.substring(SERVER_PREFIX.length()), v);
                    continue;  // 判断语句后面有代码才需要 continue 跳过当前循环
                } else if (k.startsWith(HEADERS_PREFIX)) {
                    mapHeaders.put(k.substring(HEADERS_PREFIX.length()), v);
                    continue;
                } else if (k.startsWith(DB_PREFIX)) {
                    String[] aliasKey = k.substring(DB_PREFIX.length()).split("\\.", 2);
                    if (aliasKey.length > 2) {
                        continue;
                    }  // 不符合要求跳过
                    String alias = aliasKey[0]; // NG_DB_SIT1
                    String key = aliasKey[1];
                    // 4.DBConnectionInfo 赋值
                    DBConnectionInfo info = new DBConnectionInfo();
                    if (connectionInfoMap.containsKey(alias)) {
                        info = connectionInfoMap.get(alias);
                    } else {
                        connectionInfoMap.put(alias, info);
                    }
                    switch (key) {
                        case "jdbc":
                            info.setJdbc(v);
                            break;
                        case "username":
                            info.setUsername(v);
                            break;
                        case "key":
                            info.setPassword(v);
                            break;
                    }
                    continue;
                }
                // 2.ApiMetaInfo: api.def+api.sample_folder：仅仅是作为中转使用
                switch (k) {
                    case API_PREFIX + "def":
                        apiMetaInfo.setDef(v);
                        break;
                    case API_PREFIX + "sample_folder":
                        apiMetaInfo.setSampleFolder(v);
                        break;
                    case API_PREFIX + "activation":
                        apiMetaInfo.setActivation(v);
                        break;
                }
            }
            // 1.EnvInfo 赋值
            envInfo.setServers(mapServer);
            envInfo.setHeaders(mapHeaders);
            envInfo.setApiMetaInfo(apiMetaInfo);
            // 3.ApiRunInfo 赋值
            fetchApiRunInfo(apiMetaInfo);
            fetchActivationProps(apiMetaInfo.getActivation());
            // 对 接口字段mapping
            fetchMappingsInfo(apiMetaInfo);

        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }

    // 根据配置文件的 apiMetaInfo 信息，对 ApiRunInfo 赋值
    private void fetchApiRunInfo(ApiMetaInfo apiMetaInfo) {
        String defCsv = apiMetaInfo.getDef();
        this.apiRunInfoMap = fetchDefCsv(defCsv);
    }

    // 处理 api_def.csv 获取所有接口运行信息，然后对ApiRunInfo赋值
    private Map<String, ApiRunInfo> fetchDefCsv(String defCsv) {
        InputStream in = this.getClass().getResourceAsStream("/" + defCsv);
        CsvUtils csv = new CsvUtils(in, defCsv);
        csv.ToListMap();
        Map<String, ApiRunInfo> map = new HashMap<>(); //对ApiRunInfo赋值
        for (Map<String, String> line : csv.getDataMaps()) {
            String name = line.get("name"); // 接口名
            ApiRunInfo info = new ApiRunInfo();
            map.put(name, info);
            info.setName(name);
            info.setCategory(line.get("category"));
            info.setPath(line.get("path"));
            info.setMethod(line.get("method"));
            info.setSample(line.get("sample"));
            info.setMapReq(line.get("mapReq"));
            info.setMapRes(line.get("mapRes"));
            info.setMapException(line.get("mapException"));
        }
        return map;
    }

    // 根据 ApiMetaInfo,参数模板sampleFolder，获取对应的请求参数模板
    private void fetchMappingsInfo(ApiMetaInfo apiMetaInfo) throws Exception {
        String defCsv = apiMetaInfo.getDef();
        // data/def/api_def.csv 同一目录下 data/def/mappingpath
        String mappingFolder = defCsv.substring(0, defCsv.lastIndexOf("/") + 1) + MAPPING_FOLDER;
        String fullPath = this.getClass().getResource("/" + mappingFolder).getPath();
        try {
            fullPath = URLDecoder.decode(fullPath, Constants.UTF8);
        } catch (Exception e) {
            throw e;
        }
        log.info("mappingFolder具体地址：{}", fullPath);
        File[] files = new File(fullPath).listFiles();
        // data/def/mappingpath 目录下第一层 要目录
        for (File file : files) {
            if (!file.isDirectory()) {
                continue;
            }
            File[] mappingFiles = file.listFiles();
            // data/def/mappingpath 目录下的目录里面需要csv文件
            for (File mf : mappingFiles) {
                if (mf.isDirectory()) {
                    continue;
                }
                // mfName=xxx.csv
                String mfName = mf.getName();
                try {
                    CsvUtils util = new CsvUtils(new FileInputStream(mf), mf.getPath());
                    util.ToMap("name", "jsonPath");
                    // 获取mapping文件中的 key 和value
                    Map<String, String> mapping = util.getMapColumn();
                    int iExt = mfName.lastIndexOf(".");
                    // 判断是否是文件，取mfName=文件名, 如果没有带.csv也可以
                    mfName = iExt > -1 ? mfName.substring(0, iExt) : mfName;
                    // 组装文件地址,比如：PearAPI.oil
                    this.mappingMaps.put(file.getName() + "." + mfName, mapping);
                } catch (Exception e) {
                    log.warn("File <{}> is not a valid json path mapping csv.\tException as:\n{}",
                            mfName, e.getMessage());
                }
            }
        }
    }

    // 获取证书配置信息  "data/activation/activationRequest.properties"
    private void fetchActivationProps(String activationPropFilePath) {
        Map<String, ApiActivation> map = new HashMap<>();
        if (StringUtils.isEmpty(activationPropFilePath)) {
            this.activationMap = map;
            return;
        }
        InputStream in = this.getClass().getResourceAsStream("/" + activationPropFilePath);
        Properties props = new Properties();
        try {
            Reader reader = new InputStreamReader(in, Constants.UTF8);
            props.load(reader);
            Enumeration<?> propNames = props.propertyNames();
            ApiActivation activation = null;
            while (propNames.hasMoreElements()) {
                String propName = (String) propNames.nextElement();
                if (StringUtils.isEmpty(propName)) {
                    continue;
                }
                String propValue = props.getProperty(propName);
                String[] parts = propName.split("_", 2);
                String currentName = parts[0];
                String currentSubName = parts[1];
                activation = map.getOrDefault(currentName, new ApiActivation());
                activation = convertToActivation(activation, currentSubName, propValue);
                map.put(currentName, activation);

            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        this.activationMap = map;
    }

    private ApiActivation convertToActivation(ApiActivation activation, String propName, String propValue) {
        switch (propName.toUpperCase()) {
            case "NAME":
                activation.setName(propValue);
                break;
            case "CERTIFICATE":
                activation.setCertificate(propValue);
                break;
            case "CERTKEY":
                activation.setCertKey(propValue);
                break;
            case "EMAIL":
                activation.setEmail(propValue);
                break;
            case "PRIVATEKEY":
                activation.setPrivateKey(propValue);
                break;
            case "PUBLICKEY":
                activation.setPublicKey(propValue);
                break;
            case "CSR":
                activation.setCsr(propValue);
                break;
        }
        return activation;
    }

}
